#pragma once

#include "stdafx.h"

#include <http_listener.h>
#include <filestream.h>
#include <azurestorage.h>

#include "genutils.h"

#define one_mb 1024 * 1024
#define four_mb one_mb * 4
#define sixty_four_mb one_mb * 64

class azure_store
{
protected:
	casablanca::string_t m_username;
	casablanca::string_t m_userkey;
	casablanca::string_t m_storage_url;
	casablanca::string_t m_container_name;

protected:
	azure_store(
		casablanca::string_t username,
		casablanca::string_t userkey,
		casablanca::string_t storage_url,
		casablanca::string_t container_name
	) : 
		m_username(username),
		m_userkey(userkey),
		m_storage_url(storage_url),
		m_container_name(container_name)
	{}
};

class azure_blob_store : public azure_store
{
	azure::storage::storage_credentials credentials;
	azure::storage::cloud_blob_client cloud_blob_client;
	azure::storage::cloud_blob_container cloud_blob_container;

public:
	azure_blob_store(
		casablanca::string_t username,
		casablanca::string_t userkey,
		casablanca::string_t storage_url,
		casablanca::string_t container_name
	) : azure_store(username, userkey, storage_url, container_name),
		credentials(username, userkey), 
		cloud_blob_client(storage_url, credentials), 
		cloud_blob_container(cloud_blob_client, container_name)
	{
		cloud_blob_container.create().get();
	}

public:
	/*
		Given a filename on the local machine, upload the file to azure blob storage and return the url to the blob
	*/
	casablanca::string_t save_file_to_blob(casablanca::string_t file_source)
	{
		auto file_target = general_utilities::get_unique_filename(L"",L".wav");

		azure::storage::cloud_blob _cloud_blob(cloud_blob_container, file_target);

		upload_file_to_block_blob(file_source, _cloud_blob.to_block_blob());
		
		auto url_target = get_blob_url(file_target);

		std::wcout << U("Uploaded file to ") << url_target << std::endl;

		return url_target;
	}

public:
	/*
		Given the name of a file stored in the blob, return the full url to the blob
	*/
	casablanca::string_t get_blob_url(casablanca::string_t fileName)
	{
		casablanca::stringstream_t output;
		output << m_storage_url << "/" << m_container_name << "/" << fileName;

		return output.str();
	}

protected:
	// TODO: QUESTION - review required
	void upload_file_to_block_blob(const casablanca::string_t &file_name, azure::storage::cloud_block_blob blob)
	{
		try
		{
			// TODO: QUESTION - what happened to init? Do we need to do this?
			//blob.init().wait();

			// TODO: QUESTION - do we need to do this? Can this be done as part of the framework?

			// If less than 64 MB just upload all at once, otherwise will need to break into 4 MB parts.
			auto file_buf = async::fstreambuf<async::byte>::open(file_name.c_str(), std::ios::in | std::ios::binary).get();
        
			const size_t size = (size_t) file_buf.seek(0, std::ios::end, std::ios::in);
			if(size == -1)
			{
				throw std::exception("Unable to seek");
			}
			if((size_t)file_buf.seek(0, std::ios::in) != 0)
			{
				throw std::exception("Unable to seek to beginning of file");
			}

			// If less than 64MB upload at once.
			if(size < sixty_four_mb)
			{
				std::vector<unsigned char> bytes;
				bytes.resize(size);
				file_buf.getn(&bytes[0], size).wait();
				blob.put(std::move(bytes)).wait();
			}

			// Otherwise must separate into 4 MB chunks.
			else
			{
				std::vector<azure::storage::block_info> blocks;
				std::vector<unsigned char> bytes;

				int block_count = 0;
				while(true)
				{
					bytes.resize(four_mb);
					const size_t num_bytes_read = file_buf.getn(&bytes[0], four_mb).wait();
                
					// Upload block.
					blob.put_block(block_count, std::move(bytes)).wait();
					blocks.push_back(azure::storage::block_info(block_count));

					// Check for error or end of file.
					if(num_bytes_read != four_mb)
					{
						if(num_bytes_read == -1)
						{
							throw std::exception(std::string("Error reading" + utilities::conversions::to_utf8string(file_name)).c_str());
						}
						else
						{
							break;
						}
					}
				}

				blob.put_block_list(blocks).wait();
			}
			file_buf.close().wait();
		}
		catch(std::exception &e)
		{
			std::cout << "Exception: " << e.what();
		}
	}
};